<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html lang="zh-CN"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta http-equiv="Content-Language" content="zh-CN"><link href="stylesheet.css" media="all" rel="stylesheet" type="text/css">
<title>在表之间连接</title>
<script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?d286c55b63a3c54a1e43d10d4c203e75"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script>
</head><body class="SECT1">
<div>
<table summary="Header navigation table" width="100%" border="0" cellpadding="0" cellspacing="0">
<tr><th colspan="5" align="center" valign="bottom">PostgreSQL 8.2.3 中文文档</th></tr>
<tr><td width="10%" align="left" valign="top"><a href="tutorial-select.html" accesskey="P">后退</a></td><td width="10%" align="left" valign="top"><a href="tutorial-sql.html">快退</a></td><td width="60%" align="center" valign="bottom">章2. SQL 语言</td><td width="10%" align="right" valign="top"><a href="tutorial-sql.html">快进</a></td><td width="10%" align="right" valign="top"><a href="tutorial-agg.html" accesskey="N">前进</a></td></tr>
</table>
<hr align="LEFT" width="100%"></div>
<div class="SECT1"><h1 class="SECT1"><a name="TUTORIAL-JOIN">2.6. 在表之间连接</a></h1><a name="AEN766"></a>
<p>到目前为止，我们的查询一次只访问了一个表。查询可以一次访问多个表，或者用某种方式访问一个表，而同时处理该表的多个行。一个同时访问同一个或者不同表的多个行的查询叫<i class="FIRSTTERM">连接</i>查询。举例来说，比如你想列出所有天气记录以及这些记录相关的城市。要实现这个目标，我们需要拿 weather 表每行的 city 字段和 cities 表所有行的 name 字段进行比较，并选取那些这些数值相匹配的行。</p>
<div class="NOTE">
<blockquote class="NOTE">
<p><b>【注意】</b>这里只是一个概念上的模型。连接通常以比实际比较每个可能的配对行更高效的方式执行，但这些是用户看不到的。</p>
</blockquote>
</div>
<p>这个任务可以用下面的查询来实现：</p>
<pre class="PROGRAMLISTING">SELECT *
    FROM weather, cities
    WHERE city = name;</pre>
<pre class="SCREEN">     city      | temp_lo | temp_hi | prcp |    date    |     name      | location
---------------+---------+---------+------+------------+---------------+-----------
 San Francisco |      46 |      50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
 San Francisco |      43 |      57 |    0 | 1994-11-29 | San Francisco | (-194,53)
(2 rows)</pre>
<p>观察结果集的两个方面：</p>
<ul>
<li><p>没有城市 Hayward 的结果行。这是因为在 <code class="CLASSNAME">cities</code> 表里面没有与 Hayward 匹配的行，所以连接忽略了 weather 表里的不匹配行。我们稍后将看到如何修补这个问题。</p></li>
<li><p>有两个字段包含城市名。这是正确的，因为 <code class="CLASSNAME">weather</code> 和 <code class="CLASSNAME">cities</code> 表的字段是接在一起的。不过，实际上我们不想要这些，因此你将可能希望明确列出输出字段而不是使用 <tt class="LITERAL">*</tt> ：</p>
<pre class="PROGRAMLISTING">SELECT city, temp_lo, temp_hi, prcp, date, location
    FROM weather, cities
    WHERE city = name;</pre></li>
</ul>
<div class="FORMALPARA">
<p><b>【练习】</b>看看省略 <tt class="LITERAL">WHERE</tt> 子句的含义是什么。</p>
</div>
<p>因为这些字段的名字都不一样，所以分析器自动找出它们属于哪个表，但是如果两个表中有重复的字段名，你就必须使用字段全称<i class="FIRSTTERM">限定</i>你想要的字段：</p>
<pre class="PROGRAMLISTING">SELECT weather.city, weather.temp_lo, weather.temp_hi,
       weather.prcp, weather.date, cities.location
    FROM weather, cities
    WHERE cities.name = weather.city;</pre>
<p>一般认为在连接查询里使用字段全称是很好的风格，这样，即使在将来向其中一个表里添加了同名字段也不会引起混淆。</p>
<p>到目前为止，这种类型的连接查询也可以用下面这样的形式写出来：</p>
<pre class="PROGRAMLISTING">SELECT *
    FROM weather INNER JOIN cities ON (weather.city = cities.name);</pre>
<p>这个语法并非像上面那个那么常用，我们在这里写出来是为了让你更容易了解后面的主题。</p>
<p><a name="AEN795"></a>现在我们将看看如何能把 Hayward 记录找回来。我们想让查询干的事是扫描 <code class="CLASSNAME">weather</code> 表，并且对每一行都找出匹配的 <code class="CLASSNAME">cities</code> 表里面的行。如果没有找到匹配的行，那么需要一些"空值"代替 <code class="CLASSNAME">cities</code> 表的字段。这种类型的查询叫 <i class="FIRSTTERM">外连接</i>(我们在此之前看到的连接都是内连接)。这样的命令看起来像这样：</p>
<pre class="PROGRAMLISTING">SELECT *
    FROM weather LEFT OUTER JOIN cities ON (weather.city = cities.name);

     city      | temp_lo | temp_hi | prcp |    date    |     name      | location
---------------+---------+---------+------+------------+---------------+-----------
 Hayward       |      37 |      54 |      | 1994-11-29 |               |
 San Francisco |      46 |      50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
 San Francisco |      43 |      57 |    0 | 1994-11-29 | San Francisco | (-194,53)
(3 rows)</pre>
<p>这个查询是一个<i class="FIRSTTERM">左外连接</i>，因为连接操作符(LEFT OUTER JOIN)左边的表中的行在输出中至少出现一次，而右边的表中的行只输出那些与左边的表有匹配的行。如果输出的左表中的行没有右表中的行与其对应，那么右表中的字段将填充为 NULL 。</p>
<div class="FORMALPARA">
<p><b>【练习】</b>还有右连接和全连接。试着找出来它们能干什么。</p>
</div>
<p><a name="AEN809"></a><a name="AEN812"></a>我们也可以把一个表和自己连接起来。这叫<i class="FIRSTTERM">自连接</i>。比如，假设我们想找出那些在其它天气记录的温度范围之外的天气记录。这样我们就需要拿 <code class="CLASSNAME">weather</code> 表里每行的 <tt class="STRUCTFIELD">temp_lo</tt> 和 <tt class="STRUCTFIELD">temp_hi</tt> 字段与 <code class="CLASSNAME">weather</code> 表里其它行的 <tt class="STRUCTFIELD">temp_lo</tt> 和 <tt class="STRUCTFIELD">temp_hi</tt> 字段进行比较。我们可以用下面的查询实现这个目标：</p>
<pre class="PROGRAMLISTING">SELECT W1.city, W1.temp_lo AS low, W1.temp_hi AS high,
    W2.city, W2.temp_lo AS low, W2.temp_hi AS high
    FROM weather W1, weather W2
    WHERE W1.temp_lo &lt; W2.temp_lo
    AND W1.temp_hi &gt; W2.temp_hi;

     city      | low | high |     city      | low | high
---------------+-----+------+---------------+-----+------
 San Francisco |  43 |   57 | San Francisco |  46 |   50
 Hayward       |  37 |   54 | San Francisco |  46 |   50
(2 rows)</pre>
<p>在这里我们把 weather 表重新标记为 <tt class="LITERAL">W1</tt> 和 <tt class="LITERAL">W2</tt> 以区分连接的左边和右边。你还可以用这样的别名在其它查询里节约一些敲键，比如：</p>
<pre class="PROGRAMLISTING">SELECT *
    FROM weather w, cities c
    WHERE w.city = c.name;</pre>
<p>以后会经常碰到这样的缩写。</p>
</div>
<div>
<hr align="LEFT" width="100%">
<table summary="Footer navigation table" width="100%" border="0" cellpadding="0" cellspacing="0">
<tr><td width="33%" align="left" valign="top"><a href="tutorial-select.html" accesskey="P">后退</a></td><td width="34%" align="center" valign="top"><a href="index.html" accesskey="H">首页</a></td><td width="33%" align="right" valign="top"><a href="tutorial-agg.html" accesskey="N">前进</a></td></tr>
<tr><td width="33%" align="left" valign="top">查询一个表</td><td width="34%" align="center" valign="top"><a href="tutorial-sql.html" accesskey="U">上一级</a></td><td width="33%" align="right" valign="top">聚集函数</td></tr>
</table>
</div>
</body></html>